home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2008 February / PCWFEB08.iso / Software / Freeware / Miro 1.0 / Miro_Installer.exe / xulrunner / python / storedatabase.py < prev    next >
Encoding:
Python Source  |  2007-11-12  |  46.9 KB  |  1,285 lines

  1. # Miro - an RSS based video player application
  2. # Copyright (C) 2005-2007 Participatory Culture Foundation
  3. #
  4. # This program is free software; you can redistribute it and/or modify
  5. # it under the terms of the GNU General Public License as published by
  6. # the Free Software Foundation; either version 2 of the License, or
  7. # (at your option) any later version.
  8. #
  9. # This program is distributed in the hope that it will be useful,
  10. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12. # GNU General Public License for more details.
  13. #
  14. # You should have received a copy of the GNU General Public License
  15. # along with this program; if not, write to the Free Software
  16. # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
  17.  
  18. """This module does the reading/writing of our database to/from disk.  It
  19. works with the schema module to validate the data that we read/write and with
  20. the upgradedatabase module to upgrade old database storages.
  21.  
  22. We avoid ever writing a DDB class to disk.  This allows us to change our
  23. classes without concern to how it will affect old databases.  For instance, we
  24. can delete classes and not have to worry about users with old databases that
  25. reference those classes.  Instead of class names, we write a string that
  26. represents the classes ("feed" instead of feed.Feed).  If we decide to delete
  27. the feed class, the upgrade code can handle upgrading old feed objects.
  28.  
  29. To achieve the above, before we save the DDBObjects to disk, we convert them
  30. to SavableObjects.  A SavableObject is a really simple storage container that
  31. remembers the class it was saved from and selected attributes of the object
  32. (one for each item in the object's schema).  When we restore DDBObjects, we
  33. need to convert the other way.
  34.  
  35. Right now we implement the conversion/unconversion using 2 classes
  36. (SavableConverter and SavableUnconverter) that share a base classes
  37. (ConverterBase).  Converter base handles walking the object tree, which is
  38. most of the actual conversion.  The SavableConverter and SavableUnconverter
  39. override some methods which are specific to the conversion/unconversion
  40. process.
  41.  
  42. """
  43.  
  44. import cPickle
  45. import os
  46. import traceback
  47. import shutil
  48.  
  49. import config
  50. import database
  51. import databaseupgrade
  52. import prefs
  53. import util
  54. import schema as schema_mod
  55. import eventloop
  56. import app
  57. import bsddb.db
  58. import dialogs
  59. import logging
  60. from zipfile import ZipFile
  61. import tempfile
  62. from random import randrange, seed
  63. import os.path
  64. try:
  65.     from pysqlite2 import dbapi2 as sql
  66. except ImportError:
  67.     import sqlite3 as sql
  68.  
  69. from gtcache import gettext as _
  70.  
  71. from clock import clock
  72.  
  73. # FILEMAGIC should be the first portion of the database file.  After that the
  74. # file will contain pickle data
  75. FILEMAGIC = "Democracy Database V1"
  76.  
  77. # skipOnRestore and skipUpgrade are set by the unit tests to bypass
  78. # some of our usual operations
  79. skipOnRestore = False
  80. skipUpgrade = False
  81.  
  82. class DatabaseError(Exception):
  83.     pass
  84.  
  85. class BadFileFormatError(DatabaseError):
  86.     pass
  87.  
  88. class NotImplementError(DatabaseError):
  89.     pass
  90.  
  91. # _BootStrapClass is used to as the initial class when we restore an object.
  92. class _BootStrapClass:
  93.     pass
  94.  
  95. class SavableObject:
  96.     """Object that can be safely pickled and saved to disk.  
  97.  
  98.     Member variables:
  99.  
  100.     classString -- specifies the class this object was converted from.  
  101.     savedData -- dict that stores the data we've saved.
  102.     
  103.     The SavableObject class is guarenteed to never change.  This means we can
  104.     always safely unpickle them.
  105.     """
  106.  
  107.     def __init__(self, classString):
  108.         self.classString = classString
  109.         self.savedData = {}
  110.  
  111.     def __str__(self):
  112.         return '<SavableObject: %s>' % self.classString
  113.  
  114. class ConverterBase(object):
  115.     """Base class for SavableConverter and SavableUnconverter.  It handles the
  116.     common tasks relating to converting the database to/from SavableObjects.
  117.     This include stuff like walking the object hierarchy, handling circular
  118.     references, keeping track of the path, etc.
  119.  
  120.     The subclasses of ConverterBase are responsible for creating a
  121.     convertObject method, and adding validation to the convertData method
  122.     (SavableConverter does validation at the begining, SavableUnconverter does
  123.     it at the end).
  124.     """
  125.  
  126.     def __init__(self, objectSchemas=None):
  127.         """Contruct a converter.  object schemas is a list of ObjectSchema
  128.         objects to use.  If none is given (the default), the schemas will be
  129.         taken from schema.objectSchemas.
  130.         """
  131.  
  132.         if objectSchemas is None:
  133.             objectSchemas = schema_mod.objectSchemas
  134.  
  135.         self.objectSchemaLookup = {}
  136.         self.classesToStrings = {}
  137.         self.stringsToClasses = {}
  138.         for os in objectSchemas:
  139.             self.stringsToClasses[os.classString] = os.klass
  140.             self.classesToStrings[os.klass] = os.classString
  141.             self.objectSchemaLookup[os.klass] = os
  142.  
  143.     def convertData(self, data, schema, path=""):
  144.         """Convert one piece of data.
  145.  
  146.         Arguments:
  147.             data -- piece of data to be converted
  148.             schema -- schema that the data should conform to
  149.             path -- string describing how we got to this object.  Its format
  150.                 is totally arbitrary, we just use it to help debug validation
  151.                 errors.
  152.         """
  153.  
  154.         try:
  155.             self.preValidate(data, schema)
  156.         except schema_mod.ValidationError, e:
  157.             self.handleValidationError(e, data, path, schema)
  158.  
  159.         if data is None:
  160.             rv = None
  161.         elif isinstance(schema, schema_mod.SchemaSimpleItem):
  162.             rv = data
  163.         elif isinstance(schema, schema_mod.SchemaList):
  164.             rv = self.convertList(data, schema, path)
  165.         elif isinstance(schema, schema_mod.SchemaDict):
  166.             rv = self.convertDict(data, schema, path)
  167.         elif isinstance(schema, schema_mod.SchemaObject):
  168.             rv = self.convertObject(data, schema, path)
  169.         else:
  170.             raise ValueError("%s has an unknown SchemaItem type" % schema)
  171.  
  172.         try:
  173.             self.postValidate(rv, schema)
  174.         except schema_mod.ValidationError, e:
  175.             self.handleValidationError(e, data, path, schema)
  176.         return rv
  177.  
  178.     def convertList(self, list, schema, path):
  179.         childSchema = schema.childSchema
  180.         rv = []
  181.         for i in xrange(len(list)):
  182.             child = list[i]
  183.             newPath = path + "\n[%d] -> %s" % (i, util.stringify(child))
  184.             rv.append(self.convertData(child, childSchema, newPath))
  185.         return rv
  186.  
  187.     def convertDict(self, dict, schema, path):
  188.         keySchema = schema.keySchema
  189.         valueSchema = schema.valueSchema
  190.         rv = {}
  191.         for key, value in dict.items():
  192.             # convert the key
  193.             newPath = path + "\nkey: %s" % key
  194.             newKey = self.convertData(key, keySchema, newPath)
  195.             # convert the value
  196.             newPath = path + "\n{%s} -> %s" % (util.stringify(key), util.stringify(value))
  197.             newValue = self.convertData(value, valueSchema, newPath)
  198.             # put it together
  199.             rv[newKey] = newValue
  200.         return rv
  201.  
  202.     def convertObjectList(self, objects):
  203.         """Convert a list of objects.  This is the top-level method that the
  204.         saveDatabase and restoreDatabase methods use to convert a list of
  205.         DDBObjects to/from SavableObjects.
  206.         """
  207.  
  208.         retval = []
  209.         self.memory = {}
  210.         for object, schema in self.prepareObjectList(objects):
  211.             path = "%s" % object
  212.             retval.append(self.convertData(object, schema, path))
  213.         self.onPostConversion()
  214.         return retval
  215.  
  216.     def convertObject(self, object, schema, path):
  217.         if id(object) in self.memory:
  218.             return self.memory[id(object)]
  219.  
  220.         # NOTE: we can't use the schema variable for anything here because
  221.         # object might be a subclass of the class specified in schema.
  222.         # Instead we call getObjectSchema() and use the info from there.
  223.  
  224.         try:
  225.             objectSchema = self.getObjectSchema(object)
  226.         except schema_mod.ValidationError, e:
  227.             self.handleValidationError(e, object, path, schema)
  228.  
  229.         convertedObject = self.makeNewConvert(objectSchema.classString)
  230.         self.memory[id(object)] = convertedObject
  231.  
  232.         for name, schema in objectSchema.fields:
  233.             try:
  234.                 data = self.getSourceAttr(object, name)
  235.             except schema_mod.ValidationError, e:
  236.                 self.handleValidationError(e, object, path, schema)
  237.             try:
  238.                 dataStr = str(data)
  239.             except Exception, e:
  240.                 # this will happen when data is invalid unicode
  241.                 dataStr = "<couldn't convert (%s)>" % e
  242.             newPath = path + "\n%s -> %s" % (util.stringify(name), util.stringify(dataStr))
  243.             convertedData = self.convertData(data, schema, newPath)
  244.             self.setTargetAttr(convertedObject, name, convertedData)
  245.         return convertedObject
  246.  
  247.  
  248.     # Methods that may be overridden by SavableConverter/SavableUnconverter
  249.     def preValidate(self, data, schema):
  250.         """Can be used to validate that a piece of data that is about to be
  251.         converted matches the schema for it.
  252.         """
  253.         pass
  254.  
  255.     def postValidate(self, converted, schema):
  256.         """Can be used to validate that a converted piece of data matches the
  257.         schema for it.
  258.         """
  259.         pass
  260.  
  261.     def getSourceAttr(self, object, attrName):
  262.         """Retrive the value of an attribute on a source object."""
  263.         try:
  264.             return getattr(object, attrName)
  265.         except AttributeError:
  266.             msg = "%s doesn't have the %s attribute" % (object, attrName)
  267.             raise schema_mod.ValidationError(msg)
  268.  
  269.     def setTargetAttr(self, object, attrName, attrValue):
  270.         """Set the value of an attribute on a target object."""
  271.         setattr(object, attrName, attrValue)
  272.  
  273.     def handleValidationError(self, e, object, path, schema):
  274.         reason = e.args[0]
  275.         message = """\
  276. Error validating object %r
  277.  
  278. Path:
  279. %s
  280.  
  281. Schema: %s
  282. Reason: %s""" % (object, path, schema, reason)
  283.         raise schema_mod.ValidationError(message)
  284.  
  285.     def onPostConversion(self):
  286.         """Called when the conversion process is done, just before
  287.         we return the result."""
  288.         pass
  289.  
  290.     # methods below here *must* be implemented by subclasses
  291.     def getObjectSchema(self, object):
  292.         """Get an ObjectSchema for a object to be converted."""
  293.  
  294.         raise NotImplementError()
  295.  
  296.     def prepareObjectList(self, objectList):
  297.         """Do the prep work for convertObjectList.
  298.  
  299.         Given a list of objects, return a list of (object, schema) tuples
  300.         that should be converted.
  301.         """
  302.  
  303.         raise NotImplementError()
  304.  
  305.     def makeNewConvert(self, classString):
  306.         """Construct a new object to use as our converted value.
  307.  
  308.         SavableConverter returns a SavableObject, SavableUnconverter returns a
  309.         DDBObject.
  310.         """
  311.         raise NotImplementError()
  312.  
  313. class SavableConverter(ConverterBase):
  314.     """Used to convert a list of DDBObjects into a list with the same
  315.     structure, but with DDBObject converted to SavableObjects.
  316.     """
  317.  
  318.     def prepareObjectList(self, objectList):
  319.         rv = []
  320.         for object in objectList:
  321.             if object.__class__ in self.classesToStrings:
  322.                 rv.append((object, schema_mod.SchemaObject(object.__class__)))
  323.         return rv
  324.  
  325.     def getObjectSchema(self, object):
  326.         try:
  327.             return self.objectSchemaLookup[object.__class__]
  328.         except KeyError:
  329.             # object passed schema.validate() because it was a subclass of the 
  330.             # type we're trying to save, but we don't have an ObjectSchema for
  331.             # it, so raise ValidationError here.
  332.             msg = "No ObjectSchema for %s" % object.__class__
  333.             raise schema_mod.ValidationError(msg)
  334.  
  335.     def preValidate(self, data, schema):
  336.         schema.validate(data)
  337.  
  338.     def makeNewConvert(self, classString):
  339.         return SavableObject(classString)
  340.  
  341.     def setTargetAttr(self, savable, attrName, attrValue):
  342.         savable.savedData[attrName] = attrValue
  343.  
  344. class SavableUnconverter(ConverterBase):
  345.     """Used to reverse the work of SavableConverter."""
  346.  
  347.     def prepareObjectList(self, objectList):
  348.         rv = []
  349.         for o in objectList:
  350.             klass = self.stringsToClasses[o.classString]
  351.             rv.append((o, schema_mod.SchemaObject(klass)))
  352.         return rv
  353.  
  354.     def getObjectSchema(self, object):
  355.         klass = self.stringsToClasses[object.classString]
  356.         return self.objectSchemaLookup[klass]
  357.  
  358.     def makeNewConvert(self, classString):
  359.         restored = _BootStrapClass()
  360.         restored.__class__ = self.stringsToClasses[classString]
  361.         return restored
  362.  
  363.     def getSourceAttr(self, savable, attrName):
  364.         try:
  365.             return savable.savedData[attrName]
  366.         except KeyError:
  367.             msg = "SavableObject: %s doesn't have %s " % (savable.classString,
  368.                     attrName)
  369.             raise schema_mod.ValidationError(msg)
  370.  
  371.     def postValidate(self, converted, schema):
  372.         schema.validate(converted)
  373.  
  374.     def handleValidationError(self, e, object, path, schema):
  375.         reason = e.args[0]
  376.         message = """\
  377. Error validating object %r
  378. Will use data anyway, bad things may happen soon
  379.  
  380. Path:
  381. %s
  382.  
  383. Schema: %s
  384. Reason: %s""" % (object, path, schema, reason)
  385.         raise schema_mod.ValidationWarning(message)
  386.  
  387.     def onPostConversion(self):
  388.         if not skipOnRestore:
  389.             for object in self.memory.values():
  390.                 if hasattr(object, 'onRestore'):
  391.                     object.onRestore()
  392.  
  393. def objectsToSavables(objects, objectSchemas=None):
  394.     """Transform a list of objects into something that we can save to disk.
  395.     This means converting any DDBObjects into SavebleObjects.
  396.     """
  397.  
  398.     saver = SavableConverter(objectSchemas)
  399.     return saver.convertObjectList(objects)
  400.  
  401. oneSaver = SavableConverter()
  402. oneRestorer = SavableUnconverter()
  403.  
  404. def objectToSavable(object):
  405.     """Transform a list of objects into something that we can save to disk.
  406.     This means converting any DDBObjects into SavebleObjects.
  407.     """
  408.  
  409.     global oneSaver
  410.     if object.__class__ in oneSaver.classesToStrings:
  411.         oneSaver.memory = {}
  412.         return oneSaver.convertObject(object, schema_mod.SchemaObject(object.__class__), "")
  413.     else:
  414.         return None
  415.  
  416. def savablesToObjects(savedObjects, objectSchemas=None):
  417.     """Reverses the work of objectsToSavables"""
  418.  
  419.     restorer = SavableUnconverter(objectSchemas)
  420.     restorer.objectSchemas = objectSchemas
  421.     return restorer.convertObjectList(savedObjects)
  422.  
  423. def savableToObject(savedObject):
  424.     """Transform a list of objects into something that we can save to disk.
  425.     This means converting any DDBObjects into SavebleObjects.
  426.     """
  427.  
  428.     global oneRestorer
  429.     oneRestorer.memory = {}
  430.     klass = oneRestorer.stringsToClasses[savedObject.classString]
  431.     object = oneRestorer.convertObject(savedObject, schema_mod.SchemaObject(klass), "")
  432.     oneRestorer.onPostConversion()
  433.     return object
  434.  
  435. def saveObjectList(objects, pathname, objectSchemas=None, version=None):
  436.     """Save a list of objects to disk."""
  437.  
  438.     if version is None:
  439.         version = schema_mod.VERSION
  440.     savableObjects = objectsToSavables(objects, objectSchemas)
  441.     toPickle = (version, savableObjects)
  442.     f = open(pathname, 'wb')
  443.     f.write(FILEMAGIC)
  444.     try:
  445.         cPickle.dump(toPickle, f, cPickle.HIGHEST_PROTOCOL)
  446.     finally:
  447.         f.close()
  448.  
  449. def loadPickle(pathname, objectSchemas=None):
  450.     """Restore a list of objects saved with saveObjectList."""
  451.  
  452.     f = open(pathname, 'rb')
  453.     try:
  454.         if f.read(len(FILEMAGIC)) != FILEMAGIC:
  455.             msg = "%s doesn't seem to be a democracy database" % pathname
  456.             raise BadFileFormatError(pathname)
  457.         version, savedObjects = cPickle.load(f)
  458.     finally:
  459.         f.close()
  460.  
  461.     if not skipUpgrade:
  462.         if version != schema_mod.VERSION:
  463.             shutil.copyfile(pathname, pathname + '.beforeupgrade')
  464.         databaseupgrade.upgrade(savedObjects, version)
  465.  
  466.     return savablesToObjects(savedObjects, objectSchemas)
  467.  
  468. def restoreObjectList(pathname, objectSchemas=None):
  469.     """Restore a list of objects saved with saveObjectList."""
  470.  
  471.     return loadPickle (pathname, objectSchemas)
  472.  
  473. def getObjects(pathname, convertOnFail):
  474.     """Restore a database object."""
  475.  
  476.     pathname = os.path.expanduser(pathname)
  477.     if not os.path.exists(pathname):
  478.         # maybe we crashed in saveDatabase() after deleting the real file, but
  479.         # before renaming the temp file?
  480.         tempPathname = pathname + '.temp'
  481.         if os.path.exists(tempPathname):
  482.             os.rename(tempPathname, pathname)
  483.         else:
  484.             return None # nope, there's no database to restore
  485.  
  486.  
  487.     global skipOnRestore
  488.     oldSkipOnRestore = skipOnRestore
  489.     skipOnRestore = True
  490.     try:
  491.         objects = restoreObjectList(pathname)
  492.     except BadFileFormatError:
  493.         if convertOnFail:
  494.             logging.info ("trying to convert database from old version")
  495.             import olddatabaseupgrade
  496.             olddatabaseupgrade.convertOldDatabase(pathname)
  497.             objects = restoreObjectList(pathname)
  498.             logging.info ("*** Conversion Successfull ***")
  499.         else:
  500.             raise
  501.     except ImportError, e:
  502.         if e.args == ("No module named storedatabase\r",):
  503.             # this looks like an error caused by reading a file saved in text
  504.             # mode on windows, let's try converting it.
  505.             logging.info ("trying to convert text-mode database")
  506.             f = open(pathname, 'rt')
  507.             data = f.read()
  508.             f.close()
  509.             f = open(pathname, 'wb')
  510.             f.write(data.replace("\r\n", "\n"))
  511.             f.close()
  512.             objects = restoreObjectList(pathname)
  513.         else:
  514.             raise
  515.  
  516.     import databasesanity
  517.     try:
  518.         databasesanity.checkSanity(objects, quiet=True, 
  519.                 reallyQuiet=(not util.chatter))
  520.     except databasesanity.DatabaseInsaneError, e:
  521.         util.failedExn("When restoring database", e)
  522.         # if the database fails the sanity check, try to restore it anyway.
  523.         # It's better than notheing
  524.     skipOnRestore = oldSkipOnRestore
  525.     if not skipOnRestore:
  526.         for object in objects:
  527.             if hasattr(object, 'onRestore'):
  528.                 object.onRestore()
  529.     return objects
  530.  
  531. def restoreDatabase(db=None, pathname=None, convertOnFail=True):
  532.     if db is None:
  533.         db = database.defaultDatabase
  534.     if pathname is None:
  535.         pathname = config.get(prefs.DB_PATHNAME)
  536.  
  537.     objects = getObjects (pathname, convertOnFail)
  538.     if objects:
  539.         db.restoreFromObjectList(objects)
  540.  
  541. VERSION_KEY = "Democracy Version"
  542.  
  543. class LiveStorageBDB:
  544.     TRANSACTION_TIMEOUT = 10
  545.     TRANSACTION_NAME = "Save database"
  546.  
  547.     def __init__(self, dbPath=None, restore=True):
  548.         database.confirmDBThread()
  549.         try:
  550.             self.txn = None
  551.             self.dc = None
  552.             self.toUpdate = set()
  553.             self.toRemove = set()
  554.             self.errorState = False
  555.             if dbPath is not None:
  556.                 self.dbPath = dbPath
  557.             else:
  558.                 self.dbPath = config.get(prefs.BSDDB_PATHNAME)
  559.             start = clock()
  560.             self.openEmptyDB()
  561.             if restore:
  562.                 try:
  563.                     try:
  564.                         self.db.open ("database")
  565.                         self.version = int(self.db[VERSION_KEY])
  566.                     except (bsddb.db.DBNoSuchFileError, KeyError):
  567.                         self.closeInvalidDB()
  568.                         try:
  569.                             restoreDatabase()
  570.                         except KeyboardInterrupt:
  571.                             raise
  572.                         except:
  573.                             logging.exception ("Error restoring old database")
  574.                         self.saveDatabase()
  575.                     else:
  576.                         self.loadDatabase()
  577.                 except KeyboardInterrupt:
  578.                     raise
  579.                 except databaseupgrade.DatabaseTooNewError:
  580.                     raise
  581.                 except:
  582.                     self.handleDatabaseLoadError()
  583.             else:
  584.                 self.saveDatabase()
  585.             # Since this is only used for upgrading, I'm commenting
  586.             # this out --NN
  587.             #
  588.             # eventloop.addIdle(self.checkpoint, "Remove Unused Database Logs")
  589.             end = clock()
  590.             if end - start > 0.05 and util.chatter:
  591.                 logging.timing ("Database load slow: %.3f", end - start)
  592.         except bsddb.db.DBNoSpaceError:
  593.             frontend.exit(28)
  594.  
  595.     def dumpDatabase(self, db):
  596.         from download_utils import nextFreeFilename
  597.         output = open (nextFreeFilename (os.path.join (config.get(prefs.SUPPORT_DIRECTORY), "database-dump.xml")), 'w')
  598.         global indentation
  599.         indentation = 0
  600.         def indent():
  601.             output.write('    ' * indentation)
  602.         def output_object(o):
  603.             global indentation
  604.             indent()
  605.             if o in memory:
  606.                 if o.savedData.has_key ('id'):
  607.                     output.write('<%s id="%s"/>\n' % (o.classString, o.savedData['id']))
  608.                 else:
  609.                     output.write('<%s/>\n' % (o.classString,))
  610.                 return
  611.             memory.add(o)
  612.             if o.savedData.has_key ('id'):
  613.                 output.write('<%s id="%s">\n' % (o.classString, o.savedData['id']))
  614.             else:
  615.                 output.write('<%s>\n' % (o.classString,))
  616.             indentation = indentation + 1
  617.             for key in o.savedData:
  618.                 if key == 'id':
  619.                     continue
  620.                 indent()
  621.                 output.write('<%s>' % (key,))
  622.                 value = o.savedData[key]
  623.                 if isinstance (value, SavableObject):
  624.                     output.write ('\n')
  625.                     indentation = indentation + 1
  626.                     output_object(value)
  627.                     indentation = indentation - 1
  628.                     indent()
  629.                 else:
  630.                     output.write (str(value))
  631.                 output.write ('</%s>\n' % (key,))
  632.             indentation = indentation - 1
  633.             indent()
  634.             output.write ('</%s>\n' % (o.classString,))
  635.         output.write ('<?xml version="1.0"?>\n')
  636.         output.write ('<database schema="%d">\n' % (schema_mod.VERSION,))
  637.         indentation = indentation + 1
  638.         for o in db:
  639.             global memory
  640.             memory = set()
  641.             o = objectToSavable (o)
  642.             if o is not None:
  643.                 output_object (o)
  644.         indentation = indentation - 1
  645.         output.write ('</database>\n')
  646.         output.close()
  647.  
  648.     def handleDatabaseLoadError(self):
  649.         database.confirmDBThread()
  650.         logging.exception ("exception while loading database")
  651.         self.closeInvalidDB()
  652.         self.dbenv.close()
  653.         self.saveInvalidDB()
  654.         self.openEmptyDB()
  655.         self.saveDatabase()
  656.  
  657.     def saveInvalidDB(self):
  658.         dir = os.path.dirname(self.dbPath)
  659.         saveName = "corrupt_database"
  660.         i = 0
  661.         while os.path.exists(os.path.join(dir, saveName)):
  662.             i += 1
  663.             saveName = "corrupt_database.%d" % i
  664.  
  665.         os.rename(self.dbPath, os.path.join(dir, saveName))
  666.  
  667.     def openEmptyDB(self):
  668.         database.confirmDBThread()
  669.         try:
  670.             os.makedirs(self.dbPath)
  671.         except KeyboardInterrupt:
  672.             raise
  673.         except:
  674.             pass
  675.         self.dbenv = bsddb.db.DBEnv()
  676.         self.dbenv.set_flags (bsddb.db.DB_AUTO_COMMIT | bsddb.db.DB_TXN_NOSYNC, True)
  677.         self.dbenv.set_lg_max (1024 * 1024)
  678.         self.dbenv.open (self.dbPath, bsddb.db.DB_INIT_LOG | bsddb.db.DB_INIT_MPOOL | bsddb.db.DB_INIT_TXN | bsddb.db.DB_RECOVER | bsddb.db.DB_CREATE)
  679.         self.db = bsddb.db.DB(self.dbenv)
  680.         self.closed = False
  681.  
  682.     def closeInvalidDB(self):
  683.         database.confirmDBThread()
  684.         try:
  685.             self.db.close()
  686.         except KeyboardInterrupt:
  687.             raise
  688.         except:
  689.             pass
  690.         self.db = None
  691.  
  692.     def upgradeDatabase(self):
  693.         database.confirmDBThread()
  694.         logging.info ("Upgrading database...")
  695.         savables = []
  696.         cursor = self.db.cursor()
  697.         while True:
  698.             next = cursor.next()
  699.             if next is None:
  700.                 break
  701.             key, data = next
  702.             if key != VERSION_KEY:
  703.                 try:
  704.                     savable = cPickle.loads(data)
  705.                     savables.append(savable)
  706.                 except KeyboardInterrupt:
  707.                     raise
  708.                 except:
  709.                     logging.info ('Error loading data in upgradeDatabase')
  710.                     raise
  711.         cursor.close()
  712.         changed = databaseupgrade.upgrade(savables, self.version)
  713.         
  714.         txn = self.dbenv.txn_begin()
  715.         if changed is None:
  716.             self.rewriteDatabase(savables, txn)
  717.         else:
  718.             savables_set = set()
  719.             for o in savables:
  720.                 savables_set.add(o)
  721.             for o in changed:
  722.                 if o in savables_set:
  723.                     data = cPickle.dumps(o,cPickle.HIGHEST_PROTOCOL)
  724.                     self.db.put (str(o.savedData['id']), data, txn=txn)
  725.                 else:
  726.                     try:
  727.                         self.db.delete (str(o.savedData['id']))
  728.                     except bsddb.db.DBNotFoundError:
  729.                         # If an object was created and removed during
  730.                         # upgrade, it won't be in the database to be
  731.                         # removed, so catch the exception
  732.                         pass
  733.         self.version = schema_mod.VERSION
  734.         self.db.put (VERSION_KEY, str(self.version), txn=txn)
  735.         txn.commit()
  736.         self.db.sync()
  737.  
  738.         objects = savablesToObjects (savables)
  739.         db = database.defaultDatabase
  740.         db.restoreFromObjectList(objects)
  741.  
  742.     def rewriteDatabase(self, savables, txn):
  743.         """Delete, then rewrite the entire database.  savables is a list of
  744.         SavableObjects that will be in the new database.  WARNING: This method
  745.         will probably take a long time.
  746.         """
  747.         database.confirmDBThread()
  748.         logging.info ("Rewriting database")
  749.         cursor = self.db.cursor(txn=txn)
  750.         while True:
  751.             next = cursor.next()
  752.             if next is None:
  753.                 break
  754.             cursor.delete()
  755.         cursor.close()
  756.         for o in savables:
  757.             data = cPickle.dumps(o,cPickle.HIGHEST_PROTOCOL)
  758.             self.db.put (str(o.savedData['id']), data, txn=txn)
  759.  
  760.     def loadDatabase(self):
  761.         database.confirmDBThread()
  762.         upgrade = (self.version != schema_mod.VERSION)
  763.         if upgrade:
  764.             return self.upgradeDatabase()
  765.         objects = []
  766.         cursor = self.db.cursor()
  767.         while True:
  768.             next = cursor.next()
  769.             if next is None:
  770.                 break
  771.             key, data = next
  772.             if key != VERSION_KEY:
  773.                 try:
  774.                     savable = cPickle.loads(data)
  775.                     object = savableToObject(savable)
  776.                     objects.append(object)
  777.                 except KeyboardInterrupt:
  778.                     raise
  779.                 except:
  780.                     logging.info ("Error loading data in loadDatabase")
  781.                     raise
  782.         cursor.close()
  783.         db = database.defaultDatabase
  784.         db.restoreFromObjectList(objects)
  785.  
  786.     def saveDatabase(self):
  787.         database.confirmDBThread()
  788.         db = database.defaultDatabase
  789.         self.txn = self.dbenv.txn_begin()
  790.         self.db = bsddb.db.DB(self.dbenv)
  791.         self.db.open ("database", flags = bsddb.db.DB_CREATE, dbtype = bsddb.db.DB_HASH, txn=self.txn)
  792.         for o in db.objects:
  793.             self.update(o[0])
  794.         self.version = schema_mod.VERSION
  795.         self.db.put (VERSION_KEY, str(self.version), txn=self.txn)
  796.         self.txn.commit()
  797.         self.txn = None
  798.         self.db.sync()
  799.  
  800.     def sync(self):
  801.         database.confirmDBThread()
  802.         self.db.sync()
  803.  
  804.     def close(self):
  805.         database.confirmDBThread()
  806.         self.runUpdate()
  807.         self.closed = True
  808.         self.db.close()
  809.         self.dbenv.close()
  810.  
  811.     def runUpdate(self):
  812.         database.confirmDBThread()
  813.         try:
  814.             self.txn = self.dbenv.txn_begin()
  815.             for object in self.toRemove:
  816.                 # If an object was created and removed between saves, it
  817.                 # won't be in the database to be removed, so catch the
  818.                 # exception
  819.                 try:
  820.                     self.remove (object)
  821.                 except bsddb.db.DBNotFoundError:
  822.                     pass
  823.             for object in self.toUpdate:
  824.                 self.update (object)
  825.             self.txn.commit()
  826.             self.sync()
  827.             self.txn = None
  828.             self.dc = None
  829.             self.toUpdate = set()
  830.             self.toRemove = set()
  831.             if self.errorState:
  832.                 title = _("%s database save succeeded") % (config.get(prefs.SHORT_APP_NAME), )
  833.                 description = _("The database has been successfully saved. "
  834.                                "It is now safe to quit without losing any "
  835.                                "data.")
  836.                 dialogs.MessageBoxDialog(title, description).run()
  837.                 self.errorState = False
  838.         except bsddb.db.DBNoSpaceError, err:
  839.             if not self.errorState:
  840.                 title = _("%s database save failed") % (config.get(prefs.SHORT_APP_NAME), )
  841.                 description = _("%s was unable to save its database: Disk Full.\nWe suggest deleting files from the full disk or simply deleting some movies from your collection.\nRecent changes may be lost.") % (config.get(prefs.LONG_APP_NAME)) 
  842.                 dialogs.MessageBoxDialog(title, description).run()
  843.                 self.errorState = True
  844.             try:
  845.                 self.txn.abort()
  846.             except KeyboardInterrupt:
  847.                 raise
  848.             except:
  849.                 # if we tried to do a commit and failed an abort doesn't work
  850.                 pass
  851.             self.txn = None
  852.             self.dc = eventloop.addTimeout(self.TRANSACTION_TIMEOUT, self.runUpdate, self.TRANSACTION_NAME)
  853.             
  854.  
  855.     def update (self, object):
  856.         database.confirmDBThread()
  857.         if self.closed:
  858.             return
  859.         if self.txn is None:
  860.             self.toUpdate.add (object)
  861.             if self.dc is None:
  862.                 self.dc = eventloop.addTimeout(self.TRANSACTION_TIMEOUT, self.runUpdate, self.TRANSACTION_NAME)
  863.         else:
  864.             savable = objectToSavable (object)
  865.             if savable:
  866.                 key = str(object.id)
  867.                 data = cPickle.dumps(savable,cPickle.HIGHEST_PROTOCOL)
  868.                 self.db.put (key, data, txn=self.txn)
  869.  
  870.     def remove (self, object):
  871.         database.confirmDBThread()
  872.         if self.closed:
  873.             return
  874.         if self.txn is None:
  875.             self.toRemove.add (object)
  876.             try:
  877.                 self.toUpdate.remove (object)
  878.             except KeyboardInterrupt:
  879.                 raise
  880.             except:
  881.                 pass
  882.             if self.dc is None:
  883.                 self.dc = eventloop.addTimeout(self.TRANSACTION_TIMEOUT, self.runUpdate, self.TRANSACTION_NAME)
  884.         else:
  885.             self.db.delete (str(object.id), txn=self.txn)
  886.  
  887.     def checkpoint (self):
  888.         database.confirmDBThread()
  889.         try:
  890.             if self.closed:
  891.                 return
  892.             self.dbenv.txn_checkpoint()
  893.             self.sync()
  894.             for logfile in self.dbenv.log_archive(bsddb.db.DB_ARCH_ABS):
  895.                 try:
  896.                     os.remove(logfile)
  897.                 except KeyboardInterrupt:
  898.                     raise
  899.                 except:
  900.                     pass
  901.         except bsddb.db.DBNoSpaceError:
  902.             pass
  903.         eventloop.addTimeout(60, self.checkpoint, "Remove Unused Database Logs")
  904.     def backupDatabase(self):
  905.         return None
  906.  
  907. class LiveStorage:
  908.     TRANSACTION_TIMEOUT = 10
  909.     TRANSACTION_NAME = "Save database"
  910.  
  911.     def __init__(self, dbPath=None, restore=True):
  912.         database.confirmDBThread()
  913.         try:
  914.             self.toUpdate = set()
  915.             self.toRemove = set()
  916.             self.errorState = False
  917.             self.closed = True
  918.             self.updating = False
  919.             self.dc = None
  920.             if dbPath is not None:
  921.                 self.dbPath = dbPath
  922.             else:
  923.                 self.dbPath = config.get(prefs.SQLITE_PATHNAME)
  924.             start = clock()
  925.             SQLiteDBExists = os.access(self.dbPath, os.F_OK)
  926.             self.openDatabase()
  927.             if restore:
  928.                 try:
  929.                     if SQLiteDBExists:
  930.                         self.cursor.execute("SELECT serialized_value FROM dtv_variables WHERE name=:name",{'name':VERSION_KEY})
  931.                         self.version = self.cursor.fetchone()
  932.                         if self.version:
  933.                             self.version = cPickle.loads(str(self.version[0]))
  934.                         else:
  935.                             self.version = schema_mod.VERSION
  936.                         self.loadDatabase()
  937.                     else:
  938.                         self.version = None
  939.                         if (os.access(config.get(prefs.BSDDB_PATHNAME), os.F_OK) or
  940.                             os.access(config.get(prefs.DB_PATHNAME), os.F_OK)):
  941.  
  942.                             logging.info("Upgrading from previous version of database")
  943.                             try:
  944.                                 LiveStorageBDB()
  945.                             except:
  946.                                 logging.warning("Upgrading from previous version of database failed")
  947.                         self.saveDatabase()
  948.                 except KeyboardInterrupt:
  949.                     raise
  950.                 except databaseupgrade.DatabaseTooNewError:
  951.                     raise
  952.                 except:
  953.                     self.handleDatabaseLoadError()
  954.             else:
  955.                 self.saveDatabase()
  956.             eventloop.addIdle(self.checkpoint, "Remove Unused Database Logs")
  957.             end = clock()
  958.             if end - start > 0.05 and util.chatter:
  959.                 logging.timing ("Database load slow: %.3f", end - start)
  960.         except sql.DatabaseError, e:
  961.             logging.error(e)
  962.             raise
  963.  
  964.     def openDatabase(self):
  965.         logging.info("Connecting to %s" % self.dbPath)
  966.         try:
  967.             os.makedirs(os.path.normpath(os.path.join(self.dbPath,os.path.pardir)))
  968.         except:
  969.             pass
  970.         self.conn = sql.connect(self.dbPath, isolation_level=None)
  971.         self.closed = False
  972.         self.cursor = self.conn.cursor()
  973.  
  974.         # In the future, we may need a way to upgrade this
  975.         self.cursor.execute("SELECT name FROM sqlite_master WHERE type='table'")
  976.         tables = [row[0] for row in self.cursor]
  977.         if 'dtv_objects' not in tables:
  978.             self.cursor.execute("""CREATE TABLE dtv_objects(
  979.             id INTEGER PRIMARY KEY NOT NULL,
  980.             serialized_object BLOB NOT NULL UNIQUE
  981. );""")
  982.         if 'dtv_variables' not in tables:
  983.             self.cursor.execute("""CREATE TABLE dtv_variables(
  984.             name TEXT PRIMARY KEY NOT NULL,
  985.             serialized_value BLOB NOT NULL
  986.     );""")
  987.  
  988.     def dumpDatabase(self, db):
  989.         from download_utils import nextFreeFilename
  990.         output = open (nextFreeFilename (os.path.join (config.get(prefs.SUPPORT_DIRECTORY), "database-dump.xml")), 'w')
  991.         global indentation
  992.         indentation = 0
  993.         def indent():
  994.             output.write('    ' * indentation)
  995.         def output_object(o):
  996.             global indentation
  997.             indent()
  998.             if o in memory:
  999.                 if o.savedData.has_key ('id'):
  1000.                     output.write('<%s id="%s"/>\n' % (o.classString, o.savedData['id']))
  1001.                 else:
  1002.                     output.write('<%s/>\n' % (o.classString,))
  1003.                 return
  1004.             memory.add(o)
  1005.             if o.savedData.has_key ('id'):
  1006.                 output.write('<%s id="%s">\n' % (o.classString, o.savedData['id']))
  1007.             else:
  1008.                 output.write('<%s>\n' % (o.classString,))
  1009.             indentation = indentation + 1
  1010.             for key in o.savedData:
  1011.                 if key == 'id':
  1012.                     continue
  1013.                 indent()
  1014.                 output.write('<%s>' % (key,))
  1015.                 value = o.savedData[key]
  1016.                 if isinstance (value, SavableObject):
  1017.                     output.write ('\n')
  1018.                     indentation = indentation + 1
  1019.                     output_object(value)
  1020.                     indentation = indentation - 1
  1021.                     indent()
  1022.                 else:
  1023.                     output.write (str(value))
  1024.                 output.write ('</%s>\n' % (key,))
  1025.             indentation = indentation - 1
  1026.             indent()
  1027.             output.write ('</%s>\n' % (o.classString,))
  1028.         output.write ('<?xml version="1.0"?>\n')
  1029.         output.write ('<database schema="%d">\n' % (schema_mod.VERSION,))
  1030.         indentation = indentation + 1
  1031.         for o in db:
  1032.             global memory
  1033.             memory = set()
  1034.             o = objectToSavable (o)
  1035.             if o is not None:
  1036.                 output_object (o)
  1037.         indentation = indentation - 1
  1038.         output.write ('</database>\n')
  1039.         output.close()
  1040.  
  1041.     def backupDatabase(self):
  1042.         # backs up the database and support directories to a zip file
  1043.         # returns the name of the zip file
  1044.         logging.info("Attempting to back up database")
  1045.         if not self.closed:
  1046.             try:
  1047.                 self.conn.close()
  1048.             except:
  1049.                 traceback.print_exc()
  1050.         try:
  1051.             try:
  1052.                 tempfilename = os.path.join(tempfile.gettempdir(),("%012ddatabasebackup.zip"%randrange(0,999999999999)))
  1053.                 zipfile = ZipFile(tempfilename,"w")
  1054.                 for root, dirs, files in os.walk(config.get(prefs.SUPPORT_DIRECTORY)):
  1055.                     if ((os.path.normpath(root) !=
  1056.                         os.path.normpath(config.get(prefs.ICON_CACHE_DIRECTORY)))
  1057.                         and not os.path.islink(root)):
  1058.                         relativeroot = root[len(config.get(prefs.SUPPORT_DIRECTORY)):]
  1059.                         while len(relativeroot)>0 and relativeroot[0] in ['/','\\']:
  1060.                             relativeroot = relativeroot[1:]
  1061.                         for filen in files:
  1062.                             if not os.path.islink(os.path.join(root,filen)):
  1063.                                 zipfile.write(os.path.join(root,filen),
  1064.                                               os.path.join(relativeroot, filen).encode('ascii','replace'))
  1065.                 zipfile.close()
  1066.                 logging.info("Database backed up to %s" % tempfilename)
  1067.                 return tempfilename
  1068.             except:
  1069.                 traceback.print_exc()
  1070.         finally:
  1071.             if not self.closed:
  1072.                 self.conn = sql.connect(self.dbPath, isolation_level=None)
  1073.                 self.cursor = self.conn.cursor()
  1074.         return None
  1075.  
  1076.     def handleDatabaseLoadError(self):
  1077.         database.confirmDBThread()
  1078.         logging.exception ("exception while loading database")
  1079.         self.closeInvalidDB()
  1080.         self.saveInvalidDB()
  1081.         self.saveDatabase()
  1082.  
  1083.     def saveInvalidDB(self):
  1084.         dir = os.path.dirname(self.dbPath)
  1085.         saveName = "corrupt_database"
  1086.         i = 0
  1087.         while os.path.exists(os.path.join(dir, saveName)):
  1088.             i += 1
  1089.             saveName = "corrupt_database.%d" % i
  1090.  
  1091.         os.rename(self.dbPath, os.path.join(dir, saveName))
  1092.  
  1093.     def closeInvalidDB(self):
  1094.         database.confirmDBThread()
  1095.         self.conn.close()
  1096.         self.conn = None
  1097.  
  1098.     def upgradeDatabase(self):
  1099.         database.confirmDBThread()
  1100.         self.updating = True
  1101.         self.cursor.execute("BEGIN TRANSACTION")
  1102.         try:
  1103.  
  1104.             savables = []
  1105.             self.cursor.execute("SELECT id, serialized_object FROM dtv_objects")
  1106.             for next in self.cursor:
  1107.                 key, data = next
  1108.                 try:
  1109.                     savable = cPickle.loads(str(data))
  1110.                     savables.append(savable)
  1111.                 except KeyboardInterrupt:
  1112.                     raise
  1113.                 except:
  1114.                     logging.info ('Error loading data in upgradeDatabase')
  1115.                     raise
  1116.             changed = databaseupgrade.upgrade(savables, self.version)
  1117.         
  1118.             if changed is None:
  1119.                 self.rewriteDatabase(savables)
  1120.             else:
  1121.                 savables_set = set()
  1122.                 for o in savables:
  1123.                     savables_set.add(o)
  1124.                 for o in changed:
  1125.                     if o in savables_set:
  1126.                         data = cPickle.dumps(o,cPickle.HIGHEST_PROTOCOL)
  1127.                         self.cursor.execute("REPLACE INTO dtv_objects (id, serialized_object) VALUES (?,?)",(int(o.savedData['id']), buffer(data)))
  1128.                     else:
  1129.                         self.cursor.execute("DELETE FROM dtv_objects WHERE id=?", (int(o.savedData['id']),))
  1130.             self.version = schema_mod.VERSION
  1131.             self.cursor.execute("REPLACE INTO dtv_variables (name, serialized_value) VALUES (?,?)",(VERSION_KEY, buffer(cPickle.dumps(self.version,cPickle.HIGHEST_PROTOCOL))))
  1132.  
  1133.             objects = savablesToObjects (savables)
  1134.             db = database.defaultDatabase
  1135.             db.restoreFromObjectList(objects)
  1136.         finally:
  1137.             self.updating = False
  1138.             self.cursor.execute("COMMIT")
  1139.  
  1140.     def rewriteDatabase(self, savables):
  1141.         """Delete, then rewrite the entire database.  savables is a list of
  1142.         SavableObjects that will be in the new database.  WARNING: This method
  1143.         will probably take a long time.
  1144.         """
  1145.         database.confirmDBThread()
  1146.         logging.info ("Rewriting database")
  1147.         if not self.updating:
  1148.             self.cursor.execute("BEGIN TRANSACTION")
  1149.         try:
  1150.             self.cursor.execute("DELETE FROM dtv_objects")
  1151.             for o in savables:
  1152.                 data = cPickle.dumps(o,cPickle.HIGHEST_PROTOCOL)
  1153.                 self.cursor.execute("REPLACE INTO dtv_objects (id, serialized_object) VALUES (?,?)",(int(o.savedData['id']), buffer(data)))
  1154.         finally:
  1155.             if not self.updating:
  1156.                 self.cursor.execute("COMMIT")
  1157.  
  1158.     def loadDatabase(self):
  1159.         database.confirmDBThread()
  1160.         upgrade = (self.version != schema_mod.VERSION)
  1161.         if upgrade:
  1162.             return self.upgradeDatabase()
  1163.         objects = []
  1164.         self.cursor.execute("SELECT id, serialized_object FROM dtv_objects")
  1165.         while True:
  1166.             next = self.cursor.fetchone()
  1167.             if next is None:
  1168.                 break
  1169.             key, data = next
  1170.             try:
  1171.                 savable = cPickle.loads(str(data))
  1172.                 object = savableToObject(savable)
  1173.                 objects.append(object)
  1174.             except KeyboardInterrupt:
  1175.                 raise
  1176.             except:
  1177.                 logging.info ("Error loading data in loadDatabase")
  1178.                 raise
  1179.         self.cursor.close()
  1180.         db = database.defaultDatabase
  1181.         db.restoreFromObjectList(objects)
  1182.  
  1183.     def saveDatabase(self):
  1184.         database.confirmDBThread()
  1185.         db = database.defaultDatabase
  1186.         self.updating = True
  1187.         self.cursor.execute("BEGIN TRANSACTION")
  1188.         try:
  1189.             self.cursor.execute("DELETE FROM dtv_objects WHERE 1=1")
  1190.             for o in db.objects:
  1191.                 self.update(o[0])
  1192.             self.version = schema_mod.VERSION
  1193.             self.cursor.execute("REPLACE INTO dtv_variables (name, serialized_value) VALUES (?,?)",(VERSION_KEY, buffer(cPickle.dumps(self.version,cPickle.HIGHEST_PROTOCOL))))
  1194.         finally:
  1195.             self.updating = False
  1196.             self.cursor.execute("COMMIT")
  1197.  
  1198.     def sync(self):
  1199.         database.confirmDBThread()
  1200.  
  1201.     def close(self):
  1202.         database.confirmDBThread()
  1203.         self.runUpdate()
  1204.         self.closed = True
  1205.         self.cursor.close()
  1206.         self.conn.close()
  1207.  
  1208.     def runUpdate(self):
  1209.         database.confirmDBThread()
  1210.         try:
  1211.             self.updating = True
  1212.             self.cursor.execute("BEGIN TRANSACTION")
  1213.             try:
  1214.                 for object in self.toRemove:
  1215.                     # If an object was created and removed between saves, it
  1216.                     # won't be in the database to be removed, so catch the
  1217.                     # exception
  1218.                     try:
  1219.                         self.remove (object)
  1220.                     except sql.DatabaseError, e:
  1221.                         #logging.error("SQL ERROR %s" % e)
  1222.                         pass
  1223.                 for object in self.toUpdate:
  1224.                     self.update (object)
  1225.             finally:
  1226.                 self.updating = False
  1227.                 self.cursor.execute("COMMIT")
  1228.             self.toUpdate = set()
  1229.             self.toRemove = set()
  1230.             if self.errorState:
  1231.                 title = _("%s database save succeeded") % (config.get(prefs.SHORT_APP_NAME), )
  1232.                 description = _("The database has been successfully saved. "
  1233.                                "It is now safe to quit without losing any "
  1234.                                "data.")
  1235.                 dialogs.MessageBoxDialog(title, description).run()
  1236.                 self.errorState = False
  1237.         except sql.DatabaseError, e:
  1238.             print e
  1239.             if not self.errorState:
  1240.                 title = _("%s database save failed") % (config.get(prefs.SHORT_APP_NAME), )
  1241.                 description = _("%s was unable to save its database: Disk Full.\nWe suggest deleting files from the full disk or simply deleting some movies from your collection.\nRecent changes may be lost.") % (config.get(prefs.LONG_APP_NAME)) 
  1242.                 dialogs.MessageBoxDialog(title, description).run()
  1243.                 self.errorState = True
  1244.                 self.updating=False
  1245.         self.updating=False
  1246.         self.dc = eventloop.addTimeout(self.TRANSACTION_TIMEOUT, self.runUpdate, self.TRANSACTION_NAME)
  1247.             
  1248.  
  1249.     def update (self, object):
  1250.         database.confirmDBThread()
  1251.         if self.closed:
  1252.             return
  1253.         if not self.updating:
  1254.             self.toUpdate.add (object)
  1255.             if self.dc is None:
  1256.                 self.dc = eventloop.addTimeout(self.TRANSACTION_TIMEOUT, self.runUpdate, self.TRANSACTION_NAME)
  1257.         else:
  1258.             savable = objectToSavable (object)
  1259.             if savable:
  1260.                 key = int(object.id)
  1261.                 data = cPickle.dumps(savable,cPickle.HIGHEST_PROTOCOL)
  1262.                 self.cursor.execute("REPLACE INTO dtv_objects (id, serialized_object) VALUES (?,?)",(int(key), buffer(data)))
  1263.  
  1264.     def remove (self, object):
  1265.         database.confirmDBThread()
  1266.         if self.closed:
  1267.             return
  1268.         if not self.updating:
  1269.             self.toRemove.add (object)
  1270.             try:
  1271.                 self.toUpdate.remove (object)
  1272.             except KeyboardInterrupt:
  1273.                 raise
  1274.             except:
  1275.                 pass
  1276.             if self.dc is None:
  1277.                 self.dc = eventloop.addTimeout(self.TRANSACTION_TIMEOUT, self.runUpdate, self.TRANSACTION_NAME)
  1278.         else:
  1279.             self.cursor.execute("DELETE FROM dtv_objects WHERE id=?", (int(object.id),))
  1280.  
  1281.     def checkpoint (self):
  1282.         database.confirmDBThread()
  1283.         # I don't think we have to do anything here for SQLite
  1284.         eventloop.addTimeout(60, self.checkpoint, "Remove Unused Database Logs")
  1285.